Skip to content

Fix OpenAPI3 component response conversion to emit reusable response models - #11735

Open
Vincent Biret (baywet) with Copilot wants to merge 14 commits into
mainfrom
copilot/openapi3-convert-component-responses
Open

Fix OpenAPI3 component response conversion to emit reusable response models#11735
Vincent Biret (baywet) with Copilot wants to merge 14 commits into
mainfrom
copilot/openapi3-convert-component-responses

Conversation

Copilot AI commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

The OpenAPI3 import path was flattening referenced component responses into ad hoc inline response objects, which duplicated response metadata across operations and prevented reusable response models. This made shared component responses harder to maintain and could produce awkward TypeSpec definitions when the same response was referenced repeatedly.

  • Summary

    • Importing an OpenAPI document now emits reusable TypeSpec models for #/components/responses/... references instead of inlining the response body at each operation.
    • The generated model is namespaced under Responses and preserves the status code, headers, and body schema from the component response.
    • Component response refs are deduplicated so the same shared response resolves to a single model, while still preserving the response metadata for the operation.
  • What changed

    • Added conversion logic to collect component responses during OpenAPI3 import and generate model declarations for them.
    • Routed response refs through the generated model type rather than inline expression generation when the response is a component reference.
    • Kept response headers and body mapping intact, including a preferred application/json payload when multiple content types exist.
    • Added a regression test covering a shared component response referenced from an operation.
  • Example

    namespace Responses {
      model TooManyRequests {
        @statusCode statusCode: 429;
        @header("Retry-After") @minValue(1) RetryAfter?: integer;
        @body body: ErrorResponse;
      }
    }
    
    @route("/endpoint") @get
    op endpoint(): Responses.TooManyRequests;

This keeps component responses reusable and reduces duplication while preserving the semantics of the original OpenAPI description.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added emitter:openapi3 Issues for @typespec/openapi3 emitter openapi3:converter Issues for @typespec/openapi3 openapi to typespec converter labels Aug 20, 2026
Copilot AI changed the title [WIP] Add model creation for component responses Fix OpenAPI3 component response conversion to emit reusable response models Aug 20, 2026
@baywet

Copy link
Copy Markdown
Member

Copilot add the changelog entry for this fix

@pkg-pr-new

pkg-pr-new Bot commented Aug 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/openapi3@11735

commit: fac2d04

This comment was marked as outdated.

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 20, 2026 17:31

Copilot AI commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Copilot add the changelog entry for this fix

Added the changelog entry (fix for @typespec/openapi3) in 4716093.

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings August 20, 2026 17:41
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>
@azure-sdk-automation

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings August 24, 2026 12:51

This comment was marked as outdated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comment on lines +415 to +422
if (preferredContent && preferredContent[0] !== "application/json") {
properties.push({
name: "contentType",
decorators: [{ name: "header", args: [] }],
isOptional: false,
schema: { type: "string", enum: [preferredContent[0]] },
});
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 0b5c035 by emitting the synthetic content type property as @header("Content-Type") contentType and updating the regression assertion.

Comment thread packages/openapi3/src/cli/actions/convert/utils/response-properties.ts Outdated
Comment thread packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 24, 2026 14:52
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/openapi3
Show changes

@typespec/openapi3 - fix ✏️

[converter] Emit reusable models under a Responses namespace for #/components/responses/... references instead of inlining the response at each operation

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:321

  • responses is assigned but never used. With noUnusedLocals/linting enabled this will fail the build; even without it, this local can be removed without changing behavior.
  const responses = context.openApi3Doc.components?.responses;
  if (!responses) return;

Comment thread packages/openapi3/src/cli/actions/convert/transforms/transforms.ts
Copilot AI review requested due to automatic review settings August 24, 2026 14:58
Co-authored-by: baywet <7905502+baywet@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread packages/openapi3/src/cli/actions/convert/utils/response-properties.ts Outdated
Comment thread packages/openapi3/src/cli/actions/convert/transforms/transforms.ts
Copilot AI review requested due to automatic review settings August 24, 2026 15:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:321

  • transformComponentResponses declares responses but never uses it. This adds dead code and makes it harder to see what the function actually depends on.

You can inline the existence check against context.openApi3Doc.components?.responses and remove the unused local.

  const responses = context.openApi3Doc.components?.responses;
  if (!responses) return;

packages/openapi3/src/cli/actions/convert/transforms/transforms.ts:389

  • getResponseProperties takes statusCode: string, but then passes it to convertStatusCodeToProperty, which expects Exclude<StatusCodes, "default">. This is a TypeScript type error (a plain string isn’t assignable to StatusCodes) and can break the build.

Consider typing getResponseProperties to StatusCodes (via a type query) and casting the iterated statusCode when calling it, since Object.entries(...) loses the literal key type.

function getResponseProperties(
  statusCode: string,
  response: OpenAPI3Response,
  context: Context,
): TypeSpecModelProperty[] {

Signed-off-by: Vincent Biret <vibiret@microsoft.com>
Copilot AI review requested due to automatic review settings August 24, 2026 16:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

packages/openapi3/src/cli/actions/convert/generators/generate-response-expressions.ts:48

  • The early return for #/components/responses/... refs bypasses the existing OpenAPI 3.2 SSE (text/event-stream + itemSchema) handling later in generateResponseExpressions, so SSE component responses would incorrectly return the generated response model instead of SSEStream<...>.
  if ("$ref" in props.response && props.response.$ref.startsWith("#/components/responses/")) {
    const componentResponseName = context.getComponentResponseName(props.response.$ref, statusCode);
    if (componentResponseName) {
      return [componentResponseName];
    }

packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts:285

  • This assertion hard-codes an exact newline + indentation sequence ("@error\n model ..."), which is brittle across formatting changes. Prefer a whitespace-tolerant regex so the test only cares that @error is applied to the model.
    strictEqual(
      tsp.includes("@error\n  model RejectedDefault"),
      true,
      "Expected default response model to be marked as an error: " + tsp,
    );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:openapi3 Issues for @typespec/openapi3 emitter openapi3:converter Issues for @typespec/openapi3 openapi to typespec converter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openapi3 convert - make component responses models

3 participants